﻿using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Net;
using System.Reflection;
using System.Windows;
using System.Windows.Resources;
using System.Xml;

namespace TheOliver.DigitalSignage.Client.Logic
{
    public class AssemblyLoader
    {
        private string _assemblyName;

        public void LoadAssembly(string assemblyName)
        {
            _assemblyName = assemblyName;

            Uri uri = new Uri(assemblyName, UriKind.Relative);
            
            WebClient wc = new WebClient();
            wc.OpenReadCompleted += wc_OpenReadCompleted;
            wc.OpenReadAsync(uri);
        }

        void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            WebClient wc = sender as WebClient;
            wc.OpenReadCompleted -= wc_OpenReadCompleted;

            AssemblyEventArgs assemblyArgs = new AssemblyEventArgs();

            if (e.Error != null)
            {
                OnError(e.Error.InnerException);
                return;
            }

            //if (e.Error != null)
            //{
            //    // Errors

            //    // Try to load from cache

            //    Assembly isoStorAssembly = LoadAssemblyFromIsolatedStorage(_assemblyName);
            //    if (isoStorAssembly != null)
            //    {
            //        assemblyArgs.Assembly = isoStorAssembly;
            //        assemblyArgs.LoadType = LoadType.Cached;
            //        assemblyArgs.AssemblyName = _assemblyName;
            //        if (AssemblyLoaded != null)
            //        {
            //            AssemblyLoaded(this, assemblyArgs);
            //            return;
            //        }
            //    }

            //    // If cache failed, raise error
            //    if (Error != null)
            //    {
            //        ExceptionEventArgs args = new ExceptionEventArgs();
            //        args.Exception = e.Error;
            //        Error(this, args);
            //    }
            //    return;
            //}
            //else
            //{
            //    // No Errors

            //    // Cache Assembly
            //    SaveAssemblyStream(e.Result);
            //}

            // Fire Event
            if (AssemblyLoaded != null)
            {
                //args.AssemblyStream = e.Result;
                assemblyArgs.AssemblyName = _assemblyName;
                assemblyArgs.Assembly = CreateAssembly(e.Result, _assemblyName);
                assemblyArgs.LoadType = LoadType.Downloaded;
                AssemblyLoaded(this, assemblyArgs);
            }
        }

        private void OnError(Exception exc)
        {
            if (Error != null)
            {
                ExceptionEventArgs args = new ExceptionEventArgs();
                args.Exception = exc;
                Error(this, args);
            }
        }

        //private Assembly LoadAssemblyFromIsolatedStorage(string assemblyName)
        //{
        //    IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
        //    if (file.FileExists(assemblyName))
        //    {
        //        IsolatedStorageFileStream stream = file.OpenFile(assemblyName, FileMode.Open);
        //        Assembly assembly = CreateAssembly(stream, assemblyName);
        //        return assembly;
        //    }
        //    return null;
        //}

        //private void SaveAssemblyStream(Stream stream)
        //{
        //    //IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
        //    //if (file.FileExists(_assemblyName))
        //    //{
        //    //    file.DeleteFile(_assemblyName);
        //    //}

        //    //IsolatedStorageFileStream isf = file.CreateFile(_assemblyName);
        //    //BinaryReader br = new BinaryReader(stream);

        //    //int length = (int)stream.Length;

        //    //bool enoughSpace = length < file.AvailableFreeSpace;

        //    //if (!enoughSpace)
        //    //{
        //    //    long newQuota = length * 2 + file.AvailableFreeSpace;
        //    //    IsolatedStorageQuotaWindow isqw = new IsolatedStorageQuotaWindow(newQuota, file);
        //    //    isqw.Closed += (s, e) =>
        //    //        {
        //    //            if (isqw.DialogResult.Value == true)
        //    //            {
        //    //                WriteFile(isf, br, length);
        //    //            }

        //    //        };
        //    //    isqw.Show();
        //    //}
        //    //else
        //    //{
        //    //    WriteFile(isf, br, length);
        //    //}
        //}

        private static void WriteFile(IsolatedStorageFileStream isf, BinaryReader br, int length)
        {
            byte[] bytes = br.ReadBytes(length);
            isf.Write(bytes, 0, length);
            isf.Close();
        }

        private Assembly CreateAssembly(Stream stream, string assemblyName)
        {
            StreamResourceInfo sri = new StreamResourceInfo(stream, null);
            Uri appManifestUri = new Uri("AppManifest.xaml", UriKind.Relative);

            var manifestStream = Application.GetResourceStream(sri, appManifestUri);
            string appManifest = new StreamReader(manifestStream.Stream).ReadToEnd();

            StringReader appManifestReader = new StringReader(appManifest);
            XmlReader reader = XmlReader.Create(appManifestReader);

            Assembly asm = null;

            while (reader.Read())
            {
                if (reader.IsStartElement("AssemblyPart"))
                {
                    reader.MoveToAttribute("Source");
                    reader.ReadAttributeValue();
                    string v1 = reader.Value.Substring(0, reader.Value.Length - 4);
                    string v2 = assemblyName.Substring(0, assemblyName.Length - 4);
                    if (v1.ToLower() == v2.ToLower())
                    {
                        var assemblyStream = new StreamResourceInfo(stream, "application/binary");
                        Uri uri = new Uri(reader.Value, UriKind.Relative);
                        var si = Application.GetResourceStream(assemblyStream, uri);
                        AssemblyPart p = new AssemblyPart();
                        asm = p.Load(si.Stream);
                        break;
                    }
                }
            }

            if (asm == null)
            {
                throw new InvalidOperationException("Could not find specified assembly.");
            }

            return asm;
        }

        public event EventHandler<AssemblyEventArgs> AssemblyLoaded;
        public event EventHandler<ExceptionEventArgs> Error;
    }

    public class AssemblyEventArgs : EventArgs
    {
        public string AssemblyName { get; set; }
        public Assembly Assembly { get; set; }
        public LoadType LoadType { get; set; }
    }

    public enum LoadType
    {
        Cached,
        Downloaded
    }
}

